package com.sduept.bigdata.iec61850.client;

import com.beanit.iec61850bean.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;

/**
 * 客户端服务
 *
 * @author: lxj
 * @date: 2022/3/24 8:17
 * @since
 */
public class ClientHolder {
	private static Logger logger = LoggerFactory.getLogger(ClientHolder.class);
	
    private int serverPort;
    private String serverAddress;

    private ClientSap clientSap;
    private ClientAssociation clientAssociation;
    private ServerModel model;
    private String scdFilePathName;

    private DefaultClientEventListener defaultClientEventListener;

    public ClientHolder(String serverAddress, int serverPort, String scdFilePathName) {
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;
        defaultClientEventListener = new DefaultClientEventListener(serverPort, serverAddress);
        this.scdFilePathName = scdFilePathName;
        initServer();
    }

    /**
     * 初始化连接
     */
    private void initServer() {
        try {
        	logger.info("connect server {} start!", serverAddress);
            clientSap = new ClientSap();
            InetAddress address = InetAddress.getByName(serverAddress);
            String authenticationParameter = null;
            clientAssociation = clientSap.associate(address, serverPort, null, defaultClientEventListener);
            //通过获取的scd文件获取
            model = SclParser.parse1(scdFilePathName);
            clientAssociation.setServerModel(model);
            //从客户端实时获取，速度较慢
//            model = clientAssociation.retrieveModel();
            ServerModelParser.saveToFile(model,serverAddress);
            logger.info("connect server {} successful!", serverAddress);
        } catch (Exception e) {
        	logger.error("conntect serverAddress{} 异常 ", serverAddress, e);
        }

    }

    private SclParseException getSclParseException(String msg) {
        return new SclParseException("模型解析错误,服务地址:" + serverAddress + ",文件路径:" + scdFilePathName + "," + msg);
    }

    /**
     * 获取服务端的模型
     *
     * @return Servermodel
     */
    public ServerModel getModel() {
        return model;
    }

    /**
     * 根据对象地址获取数据集
     *
     * @param objectReference
     * @return
     * @throws IOException
     * @throws SclParseException
     */
    public DataSet getDataSetValues(String objectReference) throws IOException, SclParseException {
        DataSet dataSet = model.getDataSet(objectReference);
        if (dataSet != null) {
            List<ServiceError> errors = clientAssociation.getDataSetValues(dataSet);
            if (errors != null) {
                for (ServiceError error : errors) {
                    if (error != null) {
                    	logger.error(error.toString());
                    }
                }
            }
        } else {
            throw getSclParseException("获取数据集为空,数据集引用:" + objectReference);
        }
        return dataSet;
    }

    public Collection<DataSet> getDataSets() {
        return model.getDataSets();
    }

    public Collection<Urcb> getUrcbs() {
        return model.getUrcbs();
    }

    public Collection<Brcb> getBrcbs() {
        return model.getBrcbs();
    }

    public Urcb getUrcb(String objectReference) {
        return model.getUrcb(objectReference);
    }

    public Brcb getBrcb(String objectReference) {
        return model.getBrcb(objectReference);
    }

    public void enableBRcb(String objectReference) throws ServiceError, IOException, SclParseException {
        Brcb b = model.getBrcb(objectReference);
        if (b != null) {
            clientAssociation.enableReporting(b);
            logger.info("enableBRcb sucessful ,rcb info:{}", b.toString());
        } else {
            throw getSclParseException("获取报告为空，报告地址:" + objectReference);
        }
    }

    public void enableURcb(String objectReference) throws ServiceError, IOException, SclParseException {
        Urcb u = model.getUrcb(objectReference);
        if (u != null) {
            clientAssociation.enableReporting(u);
            logger.info("enableBRcb sucessful ,rcb info:{}", u.toString());
        } else {
            throw getSclParseException("获取报告为空，报告地址:" + objectReference);
        }
    }

    public void disableURcb(String objectReference) throws ServiceError, IOException, SclParseException {
        Urcb u = model.getUrcb(objectReference);
        if (u != null) {
            clientAssociation.disableReporting(u);
            logger.info("disableRcb sucessful ,rcb info:{}", objectReference);
        } else {
            throw getSclParseException("获取报告为空，报告地址:" + objectReference);
        }
    }

    public void disableBRcb(String objectReference) throws ServiceError, IOException, SclParseException {
        Brcb b = model.getBrcb(objectReference);
        if (b != null) {
            clientAssociation.disableReporting(b);
            logger.info("disableRcb sucessful ,rcb info:{}", objectReference);
        } else {
            throw getSclParseException("获取报告为空，报告地址:" + objectReference);
        }
    }

    /**
     * 根据引用地址，查找模型节点
     *
     * @param objectReference
     * @param fc
     * @return
     * @throws ServiceError
     * @throws IOException
     */
    public ModelNode findNode(String objectReference, String fc) throws ServiceError, IOException {
        ModelNode modelNode = model.findModelNode(new ObjectReference(objectReference), Fc.valueOf(fc));
        clientAssociation.getDataValues((FcModelNode) modelNode);
        return modelNode;
    }

    /**
     * 修改数据对象,不同的数据对象要根据实际要改的类型设置修改值，例如要改的是boolean 类型，Object value 应是Boolean 类型
     *
     * @param objectReference
     * @param fc
     * @param value
     */
    public void setDataValues(String objectReference, String fc, Object value) throws ServiceError, IOException, SclParseException {
        BasicDataAttribute dataAttribute = (BasicDataAttribute) model.findModelNode(
                new ObjectReference(objectReference),
                Fc.valueOf(fc));
        if (dataAttribute == null) {
            throw getSclParseException("获取数据对象异常,数据对象地址:" + objectReference);
        }
        boolean isSetValue = true;
        switch (dataAttribute.getBasicType()) {
            case INT8:
                BdaInt8 bdaInt8 = (BdaInt8) dataAttribute;
                bdaInt8.setValue((Byte) value);
                break;
            case BOOLEAN:
                BdaBoolean bdaBoolean = (BdaBoolean) dataAttribute;
                bdaBoolean.setValue((Boolean) value);
                break;
            case VISIBLE_STRING:
                BdaVisibleString visibleString = (BdaVisibleString) dataAttribute;
                visibleString.setValue((String) value);
                break;
            case INT32:
                BdaInt32 bdaInt32 = (BdaInt32) dataAttribute;
                bdaInt32.setValue((Integer) value);
                break;
            case INT64:
                BdaInt64 bdaInt64 = (BdaInt64) dataAttribute;
                bdaInt64.setValue((Long) value);
                break;
            case FLOAT32:
                BdaFloat32 float32 = (BdaFloat32) dataAttribute;
                float32.setFloat((Float) value);
                break;
            case FLOAT64:
                BdaFloat64 float64 = (BdaFloat64) dataAttribute;
                float64.setDouble((Double) value);
                break;
            case CHECK:
                BdaCheck bdaCheck = (BdaCheck) dataAttribute;
                bdaCheck.setValue((byte[]) value);
                break;
            case INT8U:
                BdaInt8U bdaInt8U = (BdaInt8U) dataAttribute;
                bdaInt8U.setValue((Short) value);
                break;
            case QUALITY:
                BdaQuality bdaQuality = (BdaQuality) dataAttribute;
                bdaQuality.setValue((byte[]) value);
                break;
            case TRIGGER_CONDITIONS:
                BdaTriggerConditions triggerConditions = (BdaTriggerConditions) dataAttribute;
                triggerConditions.setValue((byte[]) value);
                break;
            case INT16:
                BdaInt16 bdaInt16 = (BdaInt16) dataAttribute;
                bdaInt16.setValue((Short) value);
                break;
            case INT16U:
                BdaInt16U bdaInt16u = (BdaInt16U) dataAttribute;
                bdaInt16u.setValue((Integer) value);
                break;
            case INT32U:
                BdaInt32U bdaInt32u = (BdaInt32U) dataAttribute;
                bdaInt32u.setValue((Long) value);
                break;
            case INT128:
                BdaInt128 bdaInt128 = (BdaInt128) dataAttribute;
                bdaInt128.setValue((Long) value);
                break;
            case OPTFLDS:
                BdaOptFlds optFlds = (BdaOptFlds) dataAttribute;
                optFlds.setValue((byte[]) value);
                break;
            case TIMESTAMP:
                BdaTimestamp bdaTimestamp = (BdaTimestamp) dataAttribute;
                bdaTimestamp.setInstant((Instant) value);
                break;
            case ENTRY_TIME:
                BdaEntryTime entryTime = (BdaEntryTime) dataAttribute;
                entryTime.setTimestamp((Long) value);
                break;
            case TAP_COMMAND:
                BdaTapCommand tapCommand = (BdaTapCommand) dataAttribute;
                tapCommand.setTapCommand(BdaTapCommand.TapCommand.valueOf((String) value));
                break;
            case OCTET_STRING:
                BdaOctetString octetString = (BdaOctetString) dataAttribute;
                octetString.setValue((byte[]) value);
                break;
            case DOUBLE_BIT_POS:
                BdaDoubleBitPos doubleBitPos = (BdaDoubleBitPos) dataAttribute;
                doubleBitPos.setDoubleBitPos(BdaDoubleBitPos.DoubleBitPos.valueOf((String) value));
                break;
            case UNICODE_STRING:
                BdaUnicodeString unicodeString = (BdaUnicodeString) dataAttribute;
                unicodeString.setValue((byte[]) value);
                break;
            case REASON_FOR_INCLUSION:
                BdaReasonForInclusion reasonForInclusion = (BdaReasonForInclusion) dataAttribute;
                reasonForInclusion.setValue((byte[]) value);
                break;
            default:
                isSetValue = false;
                break;
        }
        if (isSetValue) {
            clientAssociation.setDataValues(dataAttribute);
        } else {
            throw getSclParseException("没有找到数据对象的类型,数据对象地址:" + objectReference
                    + ",数据对象类型:" + dataAttribute.getBasicType().name());
        }

    }

    public List<FileInformation> getFiles(String fileDirectory) throws ServiceError, IOException {
        return clientAssociation.getFileDirectory(fileDirectory);
    }

    public void downloadFile(String serverFilePathName, String localSavePath) throws ServiceError, IOException {
        File file = new File(localSavePath);
        clientAssociation.getFile(serverFilePathName, (fileData, moreFollows) -> {
            logger.info("file data size:" + fileData.length);
            try {
                FileUtils.writeByteArrayToFile(file, fileData, true);
            } catch (IOException e) {
                return false;
            }
            return true;
        });
    }
    public String selectFcDataObject(String objectReference,String fc) throws ServiceError, IOException {
        ModelNode sg = model.findModelNode(objectReference,Fc.valueOf(fc));
        if(sg instanceof  FcModelNode){
            clientAssociation.getDataValues((FcModelNode) sg);
            System.out.println(sg.toString());
            ModelNode sbow =  sg.getChild("SBOw");
            ((BdaBoolean)sbow.getChild("ctlVal")).setValue(true);
            ModelNode origin = sbow.getChild("origin");
            ((BdaInt8)origin.getChild("orCat")).setDefault();
            ((BdaOctetString) origin.getChild("orIdent")).setValue(new byte[]{0});
            ((BdaTimestamp) sbow.getChild("T")).setInstant(Instant.now());
            short cum = ((BdaInt8U)sbow.getChild("ctlNum")).getValue();
            System.out.println(cum);
            cum++;
            ((BdaInt8U)sbow.getChild("ctlNum")).setValue(cum);
            ((BdaCheck)sbow.getChild("Check")).setDefault();
            ((BdaCheck)sbow.getChild("Check")).setInterlockCheck(true);
            ((BdaCheck)sbow.getChild("Check")).setSynchrocheck(false);
            System.out.println(sbow.toString());
            clientAssociation.setDataValues((FcModelNode) sbow);
            ConstructedDataAttribute oper;
            try {
                oper = (ConstructedDataAttribute) sg.getChild("Oper");
            } catch (Exception e) {
                throw new IllegalArgumentException("ModelNode needs to conain a child node named \"Oper\".");
            }
            ((BdaBoolean)oper.getChild("ctlVal")).setValue(true);
            ModelNode operorigin = oper.getChild("origin");
            ((BdaInt8)operorigin.getChild("orCat")).setDefault();
            ((BdaOctetString) operorigin.getChild("orIdent")).setValue(new byte[]{0});
            short opercum = ((BdaInt8U)oper.getChild("ctlNum")).getValue();
            opercum++;
            ((BdaInt8U) oper.getChild("ctlNum")).setValue(opercum);
            ((BdaTimestamp) oper.getChild("T")).setCurrentTime();
            ((BdaCheck)oper.getChild("Check")).setDefault();
            ((BdaCheck)oper.getChild("Check")).setInterlockCheck(true);
            ((BdaCheck)oper.getChild("Check")).setSynchrocheck(false);
            clientAssociation.setDataValues((FcModelNode) oper);
            return sg.toString();
        }
        throw new ServiceError(ServiceError.PARAMETER_VALUE_INAPPROPRIATE);
    }

    public void operate(String objectReference,String fc) throws ServiceError, IOException {
        ModelNode controlDataObject = model.findModelNode(objectReference,Fc.valueOf(fc));
        if(controlDataObject instanceof  FcModelNode){
            ConstructedDataAttribute oper;
            try {
                oper = (ConstructedDataAttribute) controlDataObject.getChild("Oper");
            } catch (Exception e) {
                throw new IllegalArgumentException("ModelNode needs to conain a child node named \"Oper\".");
            }
            ((BdaBoolean)oper.getChild("ctlVal")).setValue(true);
            ((BdaInt8U) oper.getChild("ctlNum")).setValue((short) 1);
            ((BdaTimestamp) oper.getChild("T")).setInstant(Instant.now());
            ((BdaCheck)oper.getChild("Check")).setValue(new byte[]{0});
            clientAssociation.setDataValues((FcModelNode) controlDataObject);
        }
        throw new ServiceError(ServiceError.PARAMETER_VALUE_INAPPROPRIATE);
    }
}
